package com.spi.sentinel;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class TokenBucketLimiter {

    @Autowired
    private RedissonClient redissonClient ;
    @Autowired
    private RedisTemplate redisTemplate ;

    //令牌桶最大流量 可放在nacos配置文件 实现动态扩容
    public static final Integer MAX_TOKEN = 2 ;
    //每次请求消耗令牌数量
    public static final Integer REQUESTED = 1 ;
    // 令牌桶限流脚本
    private static final DefaultRedisScript<Long> RATE_LIMITER_SCRIPT;
    static {
        RATE_LIMITER_SCRIPT = new DefaultRedisScript<>();
        RATE_LIMITER_SCRIPT.setScriptText(
                "local key = KEYS[1] " +
                        "local permitsPerSecond = tonumber(ARGV[1]) " +
                        "local now = tonumber(ARGV[2]) " +
                        "local requested = tonumber(ARGV[3]) " +
                        "local maxTokens = tonumber(ARGV[4]) " +

                        "local lastTokens = tonumber(redis.call('hget', key, 'tokens') or maxTokens) " +
                        "local lastRefreshed = tonumber(redis.call('hget', key, 'lastRefreshed') or now) " +

                        "local delta = math.max(0, now - lastRefreshed) " +
                        "local filledTokens = math.min(maxTokens, lastTokens + (delta * permitsPerSecond)) " +

                        "local allowed = filledTokens >= requested " +
                        "local newTokens = filledTokens " +
                        "if allowed then " +
                        "    newTokens = filledTokens - requested " +
                        "end " +

                        "redis.call('hset', key, 'tokens', newTokens) " +
                        "redis.call('hset', key, 'lastRefreshed', now) " +
                        "redis.call('expire', key, 5) " +

                        "return allowed and 1 or 0"
        );
        RATE_LIMITER_SCRIPT.setResultType(Long.class);
    }



    public boolean tryAcquire(String key, double permitsPerSecond, String message, long timeout, TimeUnit timeUnit){
        RLock lock = null ;
        Long result = null ;
        try {
            long millis = timeUnit.toMillis(timeout);
            long now = System.currentTimeMillis();
            //do {
                //加锁
                lock = redissonClient.getLock(key);
                if (lock.tryLock()){
                    //令牌桶实现限流方案
                    //核心逻辑：
                    //1：当请求进来时先去看桶中的令牌数量及上次请求时间戳
                    //2：若都为空表明是首次 赋予最大令牌数量及当前时间
                    //3：当前时间减去上次请求时间差值，乘上每秒允许请求的数量 表示这这期间内生成的令牌数量 再加上桶中已有的令牌数量 表明本次请求所持有全部的令牌数量
                    //4：与每次请求消耗的令牌数量进行比较 若大于表明消耗令牌数量 表明本次允许执行 将持有令牌数量减1 并更新桶中令牌数量及请求时间戳并设置过期时间2s
                    //核心思路：
                    // 计算从上次刷新到现在生成的令牌数
                    //long delta = Math.max(0, now - lastRefreshed);
                    // double filledTokens = Math.min(maxTokens, lastTokens + (delta * permitsPerSecond));
                    //long now = System.currentTimeMillis();
                    //System.out.println(System.currentTimeMillis()/1000);
                    result = (Long) redisTemplate.execute(
                            RATE_LIMITER_SCRIPT,
                            Collections.singletonList(key + ":lock"),
                            String.valueOf(permitsPerSecond),
                            String.valueOf(System.currentTimeMillis()/1000),
                            String.valueOf(REQUESTED),
                            String.valueOf(MAX_TOKEN)
                    );
                    if (result != null && result == 1){
                        return true ;
                    }
                }
            System.out.println("Lua脚本执行结果===" + result);
//                try {
//                    Thread.sleep(100);
//                }catch (Exception e){
//                    System.out.println("do-while循环内部报错===" + e.getMessage());
//                    return false ;
//                }
//            }while (System.currentTimeMillis() - now < millis);
            return false ;
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("限流器工作报错===" + e.getMessage());
            throw new RuntimeException(e.getMessage()) ;
        }finally {
            if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }

    }

}
